/*
 * Package il.ac.biu.cs.grossmm.api.activeData
 * File ActiveDataManagerImpl.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.flow.Converter;
import il.ac.biu.cs.grossmm.api.flow.DataPointExistsException;
import il.ac.biu.cs.grossmm.api.flow.NodeInspector;
import il.ac.biu.cs.grossmm.api.flow.NotificationPoint;
import il.ac.biu.cs.grossmm.api.flow.Notifier;
import il.ac.biu.cs.grossmm.api.flow.PersistentPointManager;
import il.ac.biu.cs.grossmm.api.flow.PointTypeMissmatchException;
import il.ac.biu.cs.grossmm.api.flow.PublicationPoint;
import il.ac.biu.cs.grossmm.api.flow.PublicationPointBase;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.SubscriptionPoint;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * Prototype active data manager implementation
 */
public class ActiveDataManagerImpl implements
		il.ac.biu.cs.grossmm.api.flow.ActiveDataManager, Component {

	private static Logger logger = Logger.getLogger("ActiveDataManagerImpl");

	/**
	 * the component manager
	 */
	protected ComponentManager container;

	/**
	 * Layers indexed by node type
	 */
	protected Map<NodeType, DataLayer> layerMap = new HashMap<NodeType, DataLayer>();

	/**
	 * Node inspector for subscribers
	 */
	protected static final MemInspector memCurInspector = new MemInspector(0);

	/**
	 * Node inspector for notifiers and publishers
	 */
	protected static final MemInspector memNewInspector = new MemInspector(1);

	/**
	 * Node manipulator
	 */
	protected static final MemNodeManpipulator memManipulator = new MemNodeManpipulator();

	/**
	 * Node inspectors indexed by implementation class for the <tt>Node</tt>
	 * interface
	 */
	protected Map<Class<? extends Node>, NodeInspector> inspectors = new HashMap<Class<? extends Node>, NodeInspector>();

	// referenced components

	/**
	 * The persistent point manager to manage persistant publication points
	 * provided by this active data manager
	 */
	protected PersistentPointManager persistentPointManager;

	/**
	 * Non-persistent publictation poinst indeced by key patterns
	 */
	protected Map<KeyPattern, MemPublicationPoint> ppByPattern;

	/**
	 * Non-persistent publictation poinst indeced by their name
	 */
	protected Map<String, MemPublicationPoint> ppByName;

	/**
	 * List of registered converters
	 */
	protected List<ConversionProcessor> converters;

	/**
	 * The CTOR
	 */
	public ActiveDataManagerImpl() {
		// register the memCurInspector for hash-set and list implementations
		// of Node
		registerInspector(HashedNode.class, memCurInspector);
		registerInspector(LinkedNode.class, memCurInspector);

		// initialize maps for non-persistent publication points
		ppByPattern = new HashMap<KeyPattern, MemPublicationPoint>();
		ppByName = new HashMap<String, MemPublicationPoint>();

		// initialize list for converters
		converters = new ArrayList<ConversionProcessor>();
	}

	/**
	 * Gets data layer by node type
	 * 
	 * @param <N>
	 *            node type generic parameter
	 * @param nodeType
	 *            the node type
	 * @return data layer for this node type
	 */
	@SuppressWarnings("unchecked")
	protected synchronized <N> DataLayer<N> getLayer(NodeType<N> nodeType) {
		// get a layer, if exists
		DataLayer<N> layer = layerMap.get(nodeType);

		// create layer if not yet exist
		if (layer == null) {
			layer = createLayer(nodeType);
		}

		return layer;
	}

	/**
	 * Creates a layer for a given node type.
	 * 
	 * @param <N>
	 *            node type generic parameter
	 * @param nodeType
	 *            the node type
	 * @return a new data layer for this node type
	 */
	@SuppressWarnings("unchecked")
	private <N> DataLayer<N> createLayer(NodeType<N> nodeType) {
		DataLayer<N> layer;

		// create layer for the specified node type
		layer = new DataLayer<N>(this, nodeType);

		// put it to the map of layers
		layerMap.put(nodeType, layer);

		// will be set to true if the conversion chains may require update
		boolean needUpdate = false;

		// for all registered converters
		for (ConversionProcessor cp : converters) {
			if (cp.dstLayer == null && cp.getDestinationType().equals(nodeType)) {
				// the newly created layer is a destination layer for the cp
				// converter
				// register it
				cp.dstLayer = layer;
				if (cp.srcLayer != null)
					cp.srcLayer.register(cp, cp.dstLayer);

				// conversion graph changed, update conversion chains required
				needUpdate = true;
			} else if (cp.srcLayer == null
					&& cp.getSourceType().equals(nodeType)) {
				// the newly created layer is a sourcew layer for the cp
				// converter
				// register it
				cp.srcLayer = layer;
				if (cp.dstLayer != null)
					cp.srcLayer.register(cp, cp.dstLayer);

				// conversion graph changed, update conversion chains required
				needUpdate = true;
			}
		}

		// conversion graph has been changed, find best conversion chains
		if (needUpdate)
			findBestChains();

		// return the newly created layer
		return layer;
	}

	public <N> SubscriptionPoint<N> createSubscriptionPoint(
			Subscriber<N> subscriber, KeyPattern pattern, NodeType<N> nodeType) {
		// data layer for thi publication point
		DataLayer<N> dataLayer = getLayer(nodeType);

		// create the subscription point
		SubscriptionPointImpl<N> sp = new SubscriptionPointImpl<N>(dataLayer,
				subscriber, pattern);

		// register the subscription point on its data layer
		dataLayer.addSubscriptionPoint(sp);

		// update data processing graph links
		updateLinks();

		// return the newly created subscription point
		return sp;
	}

	/**
	 * Updates data processing graph links
	 * 
	 */
	private void updateLinks() {
		// for all layers
		for (DataLayer layer : layerMap.values()) {
			// update links on this layer
			layer.updateLinks();
		}
	}

	public <N> void removeSubscriptionPoint(
			SubscriptionPoint<N> subscriptionPoint) {
		SubscriptionPointImpl<N> sp = (SubscriptionPointImpl<N>) subscriptionPoint;
		
		// deregister subscription point 
		sp.remove();

		// update data processing graph links
		updateLinks();
	}

	@SuppressWarnings("unchecked")
	public <N> NotificationPoint<N> createNotificationPoint(Notifier<N> notifier,
			KeyPattern pattern, NodeType<N> nodeType, double priority) {
		// get/create data layer for the notification point being created
		DataLayer<N> dataLayer = getLayer(nodeType);

		// create the notification point
		NotificationPointImpl<N> np = new NotificationPointImpl<N>(dataLayer,
				notifier, pattern, nodeType, priority);

		// register the notification point on its data layer
		dataLayer.addNotificationProvider(np);

		// update data processing graph links
		updateLinks();

		// return the newly created notification point
		return np;
	}

	@SuppressWarnings("unchecked")
	public <N> void removeNotificationPoint(
			NotificationPoint<N> notificationPoint) {
		NotificationPointImpl<N> np = (NotificationPointImpl<N>) notificationPoint;

		// deregister the notifiation point
		NodeType<N> nodeType = np.nodeType;

		DataLayer<N> dataLayer = getLayer(nodeType);
		dataLayer.removeNotificationProvider(np);

		// update data processing graph links
		updateLinks();
	}

	@SuppressWarnings("unchecked")
	public synchronized <N> PublicationPoint<N> createPublicationPoint(
			KeyPattern pattern, NodeType<N> nodeType, boolean persistent,
			String identifier) throws OperationFailedException {
		
		// does non-persistent publication point with this name already exist?
		// if yes -> throw exception
		if (ppByName.containsKey(identifier))
			throw new DataPointExistsException(identifier);

		// if peristent points are supported and there is a persistent
		// publication point with the same name -> throw exception
		if (persistentPointManager != null)
			if (persistentPointManager.pointExists(identifier))
				throw new DataPointExistsException(identifier);

		// get/create data layer for the specified node type
		DataLayer<N> dataLayer = getLayer(nodeType);

		PublicationPointBase<N> pp;

		synchronized (dataLayer) {
			if (persistent) {
				// peristent point requested 
				
				// there should not exist a non-persistent point with the
				// same pattern
				if (ppByPattern.containsKey(pattern))
					throw new PointTypeMissmatchException(identifier);

				// is there are persistent point manager?
				if (persistentPointManager == null)
					// no -> try to obtain it
					try {
						persistentPointManager = (PersistentPointManager) container
								.getComponent(PersistentPointManager.class);
					} catch (Exception e) {
						// no persistent point manager -> cannot
						// create persistent publication points
						throw new OperationFailedException(
								"No persistent manager: " + e.getMessage());
					}
					
				// delegate creation of persistent publication point
				// to the persistent point manager
				pp = persistentPointManager.create(pattern, nodeType,
						identifier);
			} else {
				// non persistent point requested

				if (persistentPointManager != null)
					if (persistentPointManager.pointExists(pattern))
						throw new PointTypeMissmatchException(identifier);
				
				// is there a non-persistent point with the same pattern
				pp = ppByPattern.get(pattern);
				if (pp == null) {
					// no -> no conflict, create the point
					pp = createMemoryPublicationPoint(pattern, identifier,
							dataLayer, nodeType);
				} else {
					// yes -> the types should match
					if (!pp.nodeType.equals(nodeType))
						throw new PointTypeMissmatchException(nodeType
								+ " clashes with " + pp.nodeType);

					// reuse the same point, but register the new identifier
					pp.addName(identifier);
					ppByName.put(identifier, (MemPublicationPoint<N>) pp);
				}
			}

			// register the publication point on its data layer
			dataLayer.addNotificationProvider(pp);
		}

		// update data processing graph links
		updateLinks();

		// return the publication point
		return pp;
	}

	/**
	 * Creates non-persistent publication point without confilct detection 
	 * @param <N>
	 * @param pattern key patter for this point
	 * @param identifier publication point identifier
	 * @param dataLayer data layer to register the publication point
	 * @param nt node type for this publication point
	 * @return the newly created publicatio point
	 */
	private <N> MemPublicationPoint<N> createMemoryPublicationPoint(
			KeyPattern pattern, String identifier, DataLayer<N> dataLayer,
			NodeType<N> nt) {
		MemPublicationPoint<N> pp;
		pp = new MemPublicationPoint<N>(dataLayer, pattern, nt, identifier);

		ppByPattern.put(pattern, pp);
		ppByName.put(identifier, pp);

		return pp;
	}

	public synchronized <N> void removePublicationPoint(String identifier)
			throws OperationFailedException {
		MemPublicationPoint mp = ppByName.get(identifier);

		// does point with this name already exist?
		if (mp != null) {
			// memory point
			mp.removeName(identifier);
			ppByName.remove(identifier);

			// if(! mp.isReachable() ) -- do something ?
		}

		if (persistentPointManager != null)
			if (persistentPointManager.pointExists(identifier)) {
				// persistent point

				persistentPointManager.remove(identifier);
			}

		// update data processing graph links
		updateLinks();
	}

	@SuppressWarnings("unchecked")
	public synchronized <N> PublicationPoint<N> getPublicationPoint(
			String identifier) throws OperationFailedException {
		PublicationPoint<N> pp = ppByName.get(identifier);

		if (pp != null)
			return pp;
		else if (persistentPointManager != null)
			return persistentPointManager.get(identifier);
		else
			return null;
	}

	public void aborted(Object hint, Exception e) {
		// TODO Implement aborted
		// 
		throw new RuntimeException("Not implemented");
	}

	public void setComponentManager(ComponentManager container) {
		this.container = container;
		try {
			persistentPointManager = (PersistentPointManager) container
					.getComponent(PersistentPointManager.class);
		} catch (Exception e) {
			persistentPointManager = null; // workaround a bug in some JVM's
			logger.warning(e.toString());
		}
	}

	public void registerInspector(Class<? extends Node> nc,
			NodeInspector inspector) {
		inspectors.put(nc, inspector);
	}

	public NodeInspector getInspector(Class<? extends Node> nc) {
		return inspectors.get(nc);
	}

	@SuppressWarnings("unchecked")
	public <Nsrc, Ndst> void registerConverter(Converter<Nsrc, Ndst> converter) {

		// get source and destination node types for the specified converter
		NodeType<Ndst> dstType = converter.getDestinationType();
		NodeType<Nsrc> srcType = converter.getSourceType();

		// get data layers for these node types
		DataLayer<Nsrc> srcLayer = layerMap.get(srcType);
		DataLayer<Ndst> dstLayer = layerMap.get(dstType);

		// create the conversion processor for this converter
		// the conversion processor essentially wraps the converter
		// and provides additional meta data
		ConversionProcessor cp = new ConversionProcessor(converter, srcLayer,
				dstLayer);

		// register the conversion processor
		converters.add(cp);

		// if noth source and destination data layers for the
		// specified converter exist -> register 
		// the converter on its source layer and find the best chains
		if (srcLayer != null && dstLayer != null) {
			srcLayer.register(cp, dstLayer);
			findBestChains();
		}

	}

	/**
	 * Finds the best conversion chains based on existing set of 
	 * converters and data processing graph
	 *
	 * @see BestConversionChainFinder
	 * 
	 */
	private void findBestChains() {
		// use BestConversionChainFinder to find the best chains
		
		BestConversionChainFinder bcf = new BestConversionChainFinder(layerMap
				.values());

		bcf.findBestChains();

		// update data processing graph links
		updateLinks();
	}
}
